﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;

namespace ScreenShark
{
    delegate bool EnumMonitorDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);

    [StructLayout(LayoutKind.Sequential)]
    struct Rect
    {
        public Rectangle ToRectangle()        
        {
            return new Rectangle(left, top, right-left, bottom-top);
        }

        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    struct MonitorInfoEx
    {
        private const int CCHDEVICENAME = 32;

        public int Size;
        public Rect Monitor;
        public Rect WorkArea;
        public uint Flags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
        public string DeviceName;
    }

    public class MonitorInfo
    {
        public MonitorInfo(Rectangle bounds, Rectangle workingArea, bool isPrimary, string deviceName)
        {
            Bounds = bounds;
            WorkingArea = workingArea;
            IsPrimary = isPrimary;
            DeviceName = deviceName;
        }

        public string DeviceName { get; private set; }
        public Rectangle Bounds { get; private set; }
        public Rectangle WorkingArea { get; private set; }
        public bool IsPrimary { get; private set; }
    }

    public class Monitor
    {
        static Monitor[] _AllMonitors;
        static Monitor _PrimaryMonitor;

        [DllImport("user32.dll")]
        static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorDelegate lpfnEnum, IntPtr dwData);

        [DllImport("user32.dll")]
        static extern bool GetMonitorInfo(IntPtr hmon, ref MonitorInfoEx mi);

        public static Monitor PrimaryMonitor
        {
            get
            {
                if (null == _PrimaryMonitor)
                {
                    foreach (var m in AllMonitors)
                    {
                        if (m.GetMonitorInfo().IsPrimary)
                        {
                            _PrimaryMonitor = m;
                            break;
                        }
                    }
                }

                return _PrimaryMonitor;
            }
        }

        public static Monitor[] AllMonitors
        {
            get
            {                
                if (null == _AllMonitors)
                {
                    List<Monitor> monitors = new List<Monitor>();

                    EnumMonitorDelegate emd = (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
                    {
                        monitors.Add(new Monitor(hMonitor));
                        return true;
                    };

                    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, emd, IntPtr.Zero);

                    _AllMonitors = monitors.ToArray();
                }

                return _AllMonitors;
            }
        }
        public static Rectangle CalculateCompleteBounds()
        {
            int left = 0, top = 0, right = 0, bottom = 0;

            foreach (var m in AllMonitors)
            {
                var wa = m.GetMonitorInfo().Bounds;

                if (wa.Left < left)
                    left = wa.Left;

                if (wa.Top < top)
                    right = wa.Top;

                if (wa.Bottom > bottom)
                    bottom = wa.Bottom;

                if (wa.Right > right)
                    right = wa.Right;
            }

            return new Rectangle(left, top, right - left, bottom - top);
        }

        public static Rectangle CalculateCompleteWorkingArea()
        {
                int left = 0, top = 0 , right = 0, bottom = 0;

                foreach (var m in AllMonitors)
                {
                    var wa = m.GetMonitorInfo().WorkingArea;
                    
                    if (wa.Left < left)
                        left = wa.Left;
                    
                    if (wa.Top < top)
                        right = wa.Top;

                    if (wa.Bottom > bottom)
                        bottom = wa.Bottom;

                    if (wa.Right > right)
                        right = wa.Right;
                }
                
                return new Rectangle(left, top, right - left, bottom - top);
        }

        public static Monitor FromPoint(Point point)
        {
            foreach (var m in AllMonitors)
            {
                var info = m.GetMonitorInfo();
                
                if (info.Bounds.Contains(point))
                    return m;
            }

            return null;
        }
        
        public IntPtr Handle { get; private set; }
        
        public MonitorInfo GetMonitorInfo()
        {
            MonitorInfoEx mi = new MonitorInfoEx();
            mi.Size = 72;

            if (GetMonitorInfo(Handle, ref mi))
            {
                return new MonitorInfo(mi.Monitor.ToRectangle(), mi.WorkArea.ToRectangle(), (mi.Flags & 1) > 0, mi.DeviceName);
            }

            throw new InvalidOperationException("GetMonitorInfo failed!");
        }

        private Monitor(IntPtr handle)
        {
            Handle = handle;
        }
    }
}
